/*  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "luge-pad.hh"
#include "luge-node.hh"
#include "luge-logging.hh"
#include <iostream>

using luge::pad;

pad::pad (const pad &that)
  : node (that.node),
    name (that.name)
{
  if (this->node != 0) {
    g_object_ref (node);
  }
}

pad::pad (GeglNode *node, const std::string &name)
  : node (node),
    name (name)
{
  if (this->node != 0) {
    g_object_ref (node);
  }
}

pad::~pad ()
{
  if (this->node != 0) {
    g_object_unref (node);
  }
}

std::string pad::to_string () const
{
  std::ostringstream oss;
  oss << '<' << (void *) this->node << '.' << this->name << '>';
  return oss.str ();
}

namespace {

/*
 * gegl-pads within lua are user data containing a pad.
 */
static const char mtname_pad[] = "-gegl:pad";

/*
 * metafunctions for the node type.
 */
static int pad_gc (lua_State *ls);
static int pad_str (lua_State *ls);

/**
 * push the metatable for nodes onto the stack.
 */
static void push_pad_metatable (lua_State *ls)
{
  if (luaL_newmetatable (ls, mtname_pad)) {
    const int pad_table = lua_absindex (ls, -1);
    lua_pushcclosure (ls, pad_gc, 0);
    lua_setfield (ls, pad_table, "__gc");
    lua_pushcclosure (ls, pad_str, 0);
    lua_setfield (ls, pad_table, "__tostring");
  }
}

static int pad_gc (lua_State *ls)
{
  pad *pptr = static_cast <pad *> (luaL_checkudata (ls, 1, mtname_pad));
  pptr->~pad ();
  return 0;
}

static int pad_str (lua_State *ls)
{
  const pad &p = luge::to_pad (ls, 1);
  lua_pushstring (ls, p.to_string ().c_str ());
  return 1;
}

} /* local namespace */

bool luge::is_io_prop (GeglNode *node, const std::string &prop)
{
  return gegl_node_has_pad (node, prop.c_str ());
}

bool luge::is_input_prop (GeglNode *node, const std::string &prop)
{
  return is_io_prop (node, prop) && !is_output_prop (node, prop);
}


bool luge::is_output_prop (GeglNode *node, const std::string &prop)
{
  /*
   * @todo: HTF should we find out if this is an input or output?
   */
  return is_io_prop (node, prop) && prop == "output";
}

pad luge::get_output_pad (GeglNode *node, const std::string &prop)
{
  if (is_output_prop (node, prop)) {
    return pad (node, prop);
  } else if (is_input_prop (node, prop)) {
    gchar *source_pad = 0;
    GeglNode *source_node = gegl_node_get_producer
      (node, const_cast <gchar *> (prop.c_str ()), &source_pad);
    if (source_node != 0) {
      pad output_pad (source_node, source_pad);
      g_free (source_pad);
      return output_pad;
    } else {
      return pad (0, "");
    }
  } else {
    lg::error () << "error: not a pad: '" << prop << "'\n";
    return pad (0, "");
  }
}

/**
 * return the object at index idx which must be a pad.
 */
pad luge::to_pad (lua_State *ls, int idx)
{
  if (is_pad (ls, idx)) {
    return *static_cast <pad *> (lua_touserdata (ls, idx));
  } else if (is_node (ls, idx)) {
    return pad (luge::to_node (ls, idx), "output");
  } else {
    luaL_error (ls, "output pad expected.");
    return pad (0, std::string ());
  }
}

/**
 * push a pad object onto the lua stack.
 */
void luge::push_pad (lua_State *ls, const pad &p)
{
  void *paddr = lua_newuserdata (ls, sizeof p);
  new (paddr) pad (p);
  push_pad_metatable (ls);
  lua_setmetatable (ls, -2);
}

/**
 * return true, iff the object at index idx is a pad.
 */
bool luge::is_pad (lua_State *ls, int idx)
{
  return luaL_testudata (ls, idx, mtname_pad) != 0;
}

void luge::init_pad (lua_State *ls)
{
  push_pad_metatable (ls);
  lua_pop (ls, 1);
}
